/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2020年9月3日
 */
package com.massyframework.beanskin.runtime.modular.assembly.handling;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;

import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.assembly.AssemblyActivatedEvent;
import com.massyframework.beanskin.assembly.AssemblyContext;
import com.massyframework.beanskin.assembly.AssemblyDeactivatingEvent;
import com.massyframework.beanskin.assembly.AssemblyReadiedEvent;
import com.massyframework.beanskin.assembly.AssemblyStatus;
import com.massyframework.beanskin.assembly.AssemblyUnreadyingEvent;
import com.massyframework.beanskin.assembly.handling.ActivationHandler;
import com.massyframework.beanskin.assembly.handling.DependencyServiceHandler;
import com.massyframework.beanskin.assembly.handling.EnvironmentHandler;
import com.massyframework.beanskin.assembly.handling.Handler;
import com.massyframework.beanskin.assembly.handling.LifecycleManager;
import com.massyframework.beanskin.assembly.handling.ReadyingHandler;
import com.massyframework.beanskin.runtime.ModuleEventPublisher;
import com.massyframework.beanskin.util.classloading.ClassLoaderUtils;


/**
 * 缺省的生命周期管理
 * 
 * <p>
 * 管理装配件的生命周期，提供启动/停止装配件运行的能力
 * @author huangkh
 *
 */
final class DefaultLifecycleManager extends LifecycleEventPublisher implements LifecycleManager {

	private final Assembly assembly;
	private final Logger logger;
	private final AtomicReference<AssemblyStatus> status = new AtomicReference<>();
	
	/**
	 * 构造方法
	 * @param assembly [@link Assembly}
	 */
	public DefaultLifecycleManager(Assembly assembly) {
		this.assembly = Objects.requireNonNull(assembly, "\"assembly\" cannot be null.");
		this.logger = assembly.getLogger();
		this.status.set(AssemblyStatus.RESOLVED);
	}

	@Override
	public synchronized void start() throws Exception {
		Assembly assembly = this.getAssociatedAssembly();
        ClassLoader contextLoader =
                ClassLoaderUtils.setThreadContextClassLoader(assembly.getModuleClassLoader());
        try {
            if (this.status.get() == AssemblyStatus.RESOLVED) {
                this.status.set(AssemblyStatus.PREPARING);
            }
            if (this.isPrepared()) {
                this.doStart();
            }
        }finally {
            ClassLoaderUtils.setThreadContextClassLoader(contextLoader);
        }
	}

    /**
     * 检查是否完成准备工作
     * @return {@link boolean}, 返回<code>true</code>表示准备就绪，返回<code>false</code>表示未准备就绪
     */
    private synchronized boolean isPrepared() throws Exception{
        if (this.getAssemblyStatus() == AssemblyStatus.PREPARING){
            DependencyServiceHandler dependencyHandler =
                    this.findHandler(DependencyServiceHandler.class);
            if (dependencyHandler != null) {
                if (!dependencyHandler.isAllMatched()) {
                    return false;
                }
            }

            // 就绪前处理
            List<ReadyingHandler> handlers = this.getHandlers(ReadyingHandler.class);
            for (ReadyingHandler handler : handlers) {
                handler.readying();
            }

            this.status.set(AssemblyStatus.READIED);
            this.logDebug(this.getAssociatedAssembly()+ " is readied.");

            //在装配件内部发布就绪事件
            this.applyReadiedEvent();
            //向外部发布事件
            this.publishReadiedEvent();

            return true;
        }
        return this.getAssemblyStatus() == AssemblyStatus.READIED;
    }

    /**
     * 执行启动
     * @throws Exception 发生的非预期例外
     */
    private synchronized void doStart() throws Exception {
        if (this.getAssemblyStatus() == AssemblyStatus.READIED) {
            if (this.envIsReady()) {
                //进入工作状态前置处理
                List<ActivationHandler> handlers = this.getHandlers(ActivationHandler.class);
                for (ActivationHandler actHandler : handlers) {
                    try {
                        actHandler.doStarting();
                    }catch(Throwable e){
                        throw e;
                    }
                }

                //内部发布装配件激活事件
                this.applyActivatedEvent();

                this.getHandler(AssemblyContext.class);
                this.status.set(AssemblyStatus.WORKING);
                this.logInfo(this.getAssociatedAssembly() +" is working.");

                //向外部发布装配件激活事件
                this.publishActivedEvent();
            }
        }
    }

    /**
     * 周边环境是否就绪.
     *
     * @return {@link boolean}, <code>true</code>完成，可以进入工作状态,
     *         <code>false</code>未完成，不能进入工作状态
     */
    private boolean envIsReady() {
        List<EnvironmentHandler> handlers = this.getHandlers(EnvironmentHandler.class);
        if (!handlers.isEmpty()) {
            for (EnvironmentHandler handler: handlers) {
                if (!handler.isReady()) {
                    return false;
                }
            }
        }
        return true;
    }
    
    @Override
	public synchronized void stop() throws Exception {
    	Assembly assembly = this.getAssociatedAssembly();
    	 ClassLoader contextLoader =
                 ClassLoaderUtils.setThreadContextClassLoader(assembly.getModuleClassLoader());
         try {
             this.doStop();
             this.doUnready();
         }finally {
             ClassLoaderUtils.setThreadContextClassLoader(contextLoader);
         }
        
	}

    /**
     * 执行启动
     */
    private synchronized void doStop() throws Exception{
        if (this.getAssemblyStatus() == AssemblyStatus.WORKING) {
            //先向外部发布退出工作状态事件
            this.publishDeactivatingEvent();

            //内部发布退出工作状态事件
            this.applyInactivatingEvent();
            this.status.set(AssemblyStatus.READIED);
            this.logDebug(this.getAssociatedAssembly() + " is readied.");

            //执行退出工作状态后置处理
            List<ActivationHandler> handlers = this.getHandlers(ActivationHandler.class);
            for (ActivationHandler actHandler : handlers) {
                try {
                    actHandler.doStopped();
                } catch (Throwable e) {
                    this.logWarn(actHandler + " doStopped() failed.", e);
                    throw e;
                }
            }
        }
    }

    /**
     * 执行退出就绪状态
     */
    private synchronized void doUnready() throws Exception {
        if (this.getAssemblyStatus() == AssemblyStatus.READIED) {
            DependencyServiceHandler handler =
                    this.findHandler(DependencyServiceHandler.class);
            if (handler != null) {
                if (!handler.isAllMatched()) {

                    //向外部发布事件
                    this.publishUnreadlyingEvent();

                    //向内部发布退出就绪状态事件
                    this.applyUnreadyingEvent();
                    this.status.set(AssemblyStatus.PREPARING);

                    // 执行退出就绪状态后置处理
                    List<ReadyingHandler> handlers = this.getHandlers(ReadyingHandler.class);
                    for (ReadyingHandler rh : handlers) {
                        rh.unreadyed();
                    }
                    this.logDebug(this.getAssemblyStatus() + " is prepared.");
                }
            }
        }
    }
    
    /**
     * 对外发布装配件就绪事件
     */
    protected void publishReadiedEvent() {
    	AssemblyReadiedEvent event =
    			new AssemblyReadiedEvent(this.assembly);
    	ModuleEventPublisher publisher =
    			this.getHandler(ModuleEventPublisher.class);
    	publisher.applyEventAsync(event);
    	
    }
    
    /**
     * 对外发布装配件激活事件
     */
    protected void publishActivedEvent() {
    	AssemblyActivatedEvent event =
    			new AssemblyActivatedEvent(this.assembly);
    	ModuleEventPublisher publisher =
    			this.getHandler(ModuleEventPublisher.class);
    	publisher.applyEventAsync(event);		
    }
    
    /**
     * 对外发布装配件取消激活事件
     */
    protected void publishDeactivatingEvent() {
    	AssemblyDeactivatingEvent event =
    			new AssemblyDeactivatingEvent(this.assembly);
    	ModuleEventPublisher publisher =
    			this.getHandler(ModuleEventPublisher.class);
    	publisher.applyEvent(event);		
    }
    
    /**
     * 对外发布装配件退出就绪状态事件
     */
    protected void publishUnreadlyingEvent() {
    	AssemblyUnreadyingEvent event =
    			new AssemblyUnreadyingEvent(this.assembly);
    	ModuleEventPublisher publisher =
    			this.getHandler(ModuleEventPublisher.class);
    	publisher.applyEvent(event);		
    }
    
	@Override
	public Assembly getAssociatedAssembly() {
		return this.assembly;
	}

	@Override
	public AssemblyStatus getAssemblyStatus() {
		return this.status.get();
	}

	@Override
	protected void logTrace(String message) {
		if (logger.isTraceEnabled()){
            logger.trace(message);
        }
	}

	@Override
	protected void logDebug(String message) {
		if (logger.isDebugEnabled()){
            logger.debug(message);
        }
	}
	
	@Override
	protected void logInfo(String message) {
		if (logger.isInfoEnabled()){
            logger.info(message);
        }
	}


	@Override
	protected void logWarn(String message, Throwable cause) {
		if (logger.isWarnEnabled()){
            logger.warn(message, cause);
        }
	}
	
	protected void logError(String message, Throwable cause) {
		if (logger.isErrorEnabled()) {
			logger.error(message, cause);
		}
	}

	@Override
	protected void initHandler(Object handler) throws Exception {
		if (handler instanceof Handler) {
			((Handler)handler).init(this);
		}
	}


	@Override
	protected void destroyHandler(Object handler) {
		if (handler instanceof Handler) {
			((Handler)handler).destroy();;
		}
	}
}
